﻿using System;
using System.Xml;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.ComponentModel;

namespace Infrastructure.Serialization
{
    public class Serializer : ISerializer
    {
        public XmlNode Serialize(object obj, XmlNode parentNode)
        {
            var xmlDoc = parentNode is XmlDocument ? (XmlDocument)parentNode : parentNode.OwnerDocument;
            var node = xmlDoc.CreateElement(obj.GetType().Name);

            node.Attributes.Append(xmlDoc.CreateAttribute("class"));
            node.Attributes["class"].Value = GetFullTypeName(obj);

            foreach (var property in GetStoredProperties(obj))
            {
                var xmlAttribute = xmlDoc.CreateAttribute(property.Name);
                var value = property.GetValue(obj, null);
                xmlAttribute.Value = (value == null) ? string.Empty : value.ToString();
                node.Attributes.Append(xmlAttribute);
            }

            var serializable = obj as ISerializable;
            if (serializable != null)
            {
                serializable.OnSerialize(this, node);
            }

            parentNode.AppendChild(node);
            return node;
        }

        public object Deserialize(XmlNode node)
        {
            var type = Type.GetType(node.Attributes["class"].Value);
            var obj = Activator.CreateInstance(type);

            foreach (var property in GetStoredProperties(obj))
            {
                var attribute = node.Attributes[property.Name];
                if (attribute == null) continue;
                var value = GetValue(attribute.Value, property.PropertyType);
                property.SetValue(obj, value, null);
            }

            var serializable = obj as ISerializable;
            if (serializable != null) serializable.OnDeserialize(this, node);

            return obj;
        }

        private string GetFullTypeName(object obj)
        {
            return string.Format("{0}, {1}", obj.GetType().FullName, obj.GetType().Assembly.GetName().Name);
        }

        private IEnumerable<PropertyInfo> GetStoredProperties(object obj)
        {
            return obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                .Where(pi => pi.GetCustomAttributes(true).OfType<StoredValueAttribute>().Any());
        }

        private object GetValue(string value, Type targetType)
        {
            if (targetType.IsInstanceOfType(value)) return value;
            if (targetType == typeof(string)) return value.ToString();

            var converter = TypeDescriptor.GetConverter(targetType);
            if (converter.CanConvertFrom(value.GetType())) return converter.ConvertFrom(value);
            if (targetType.IsValueType) return converter.ConvertFromInvariantString(value.ToString());

            return null;
        }
    }
}